Skip to content

[model] support TeleChat3#9727

Open
allplepine wants to merge 3 commits into
modelscope:mainfrom
allplepine:support-telechat3
Open

[model] support TeleChat3#9727
allplepine wants to merge 3 commits into
modelscope:mainfrom
allplepine:support-telechat3

Conversation

@allplepine

@allplepine allplepine commented Jul 10, 2026

Copy link
Copy Markdown

PR type

  • Bug Fix
  • New Feature
  • Document Updates
  • More Models or Datasets Support

PR information

Support the TeleChat3 dense, coder, and MoE thinking models:

  • TeleAI/TeleChat3-36B-Thinking
  • TeleAI/TeleChat3-Coder-36B-Thinking
  • TeleAI/TeleChat3-105B-A4.7B-Thinking

This PR adds:

  • Native Swift templates for TeleChat3 and TeleChat3-Coder, aligned with their official Jinja templates.
  • Agent templates for both tool-call formats, including parallel tool calls and tool responses.
  • Model/template registration and the Chinese/English supported-model documentation.
  • Focused alignment and inference tests in tests/test_align/test_template/test_agent.py.

TeleChat3-Coder's parser needs the request tool schema to preserve string-typed arguments exactly as specified by the model's official parser. The first commit therefore adds a backward-compatible get_toolcall_with_tools(response, tools=None) hook and passes the current request's tools through the Transformers, vLLM, LMDeploy, SGLang, and GRPO-vLLM inference paths. The base implementation delegates to the existing get_toolcall, so existing agent templates retain their previous behavior; only TeleChat3-Coder overrides the schema-aware hook.

These inference-engine changes only propagate parser context; they do not claim that every TeleChat3 checkpoint is loadable by every backend. Runtime validation in this PR uses TransformersEngine, while accelerated-backend model compatibility remains determined by the corresponding vLLM, SGLang, or LMDeploy runtime.

Template behavior covered by the tests includes system/empty-system prompts, thinking removal and normalization, answer whitespace, current and historical thinking, assistant text before tool calls, single/parallel tool calls and responses, string arguments, and generation prompts. Native Swift rendering is compared with tokenizer.apply_chat_template/the model Jinja output at the input_ids level.

Experiment results

  • pre-commit run --all-files: passed.
  • Focused latest test run: 2 passed, 2 warnings:
    • tests/test_align/test_template/test_agent.py::test_telechat3
    • tests/test_align/test_template/test_agent.py::test_telechat3_coder
  • Broader local dataset alignment: 108 Swift/Jinja cases passed.
  • CPU smoke validation passed for dense/Coder forward, dense/Coder/MoE training loss, one-step dense/Coder LoRA SFT, checkpoint save, LoRA merge, merged-model reload, and generation.
  • The two 36B TransformersEngine inference tests passed on Slurm GPUs: 2 passed, 2 warnings.

Copilot AI review requested due to automatic review settings July 10, 2026 12:37

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for the TeleChat3 model family, including base, thinking, and coder variants. It implements the corresponding templates and agent templates, updates the inference engines to support tool parsing with schema validation, and adds comprehensive test coverage. The review feedback suggests adding defensive checks when unwrapping tools to prevent potential AttributeError exceptions, and introducing a safe content concatenation helper to avoid TypeError crashes when merging message contents of mixed types.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +107 to +109
tool = self.unwrap_tool(tool)
if self._get_tool_name(tool) != tool_name:
continue

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Add a defensive check to ensure tool is a dictionary after calling unwrap_tool. If unwrap_tool returns None or a non-dictionary object, calling _get_tool_name(tool) will raise an AttributeError.

Suggested change
tool = self.unwrap_tool(tool)
if self._get_tool_name(tool) != tool_name:
continue
tool = self.unwrap_tool(tool)
if not isinstance(tool, dict):
continue
if self._get_tool_name(tool) != tool_name:
continue

Comment on lines +225 to +229
@staticmethod
def _to_content_segments(content):
if isinstance(content, list) and (not content or not isinstance(content[0], int)):
return content.copy()
return [content]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Add a helper method _concat_content to safely concatenate message contents. This avoids potential TypeError exceptions when merging consecutive assistant or user messages where one content is a list (e.g., multimodal or token IDs) and the other is a string.

Suggested change
@staticmethod
def _to_content_segments(content):
if isinstance(content, list) and (not content or not isinstance(content[0], int)):
return content.copy()
return [content]
@staticmethod
def _to_content_segments(content):
if isinstance(content, list) and (not content or not isinstance(content[0], int)):
return content.copy()
return [content]
@staticmethod
def _concat_content(pre_content, content):
if isinstance(pre_content, list) and isinstance(content, list):
return pre_content + content
if isinstance(pre_content, list):
return pre_content + [content]
if isinstance(content, list):
return [pre_content] + content
return pre_content + content

Comment thread swift/template/templates/llm.py Outdated
Comment on lines +283 to +292
pre_message['content'] = pre_content + content
tool_calls = list(pre_message.get('tool_calls') or []) + list(message.get('tool_calls') or [])
if tool_calls:
pre_message['tool_calls'] = tool_calls
pre_reasoning = pre_message.get('reasoning_content')
reasoning = message.get('reasoning_content')
if isinstance(reasoning, str):
pre_message['reasoning_content'] = (pre_reasoning or '') + reasoning
else:
pre_message['content'] = pre_content + content

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Use the safe _concat_content helper to merge consecutive user/assistant messages, preventing potential TypeError crashes.

Suggested change
pre_message['content'] = pre_content + content
tool_calls = list(pre_message.get('tool_calls') or []) + list(message.get('tool_calls') or [])
if tool_calls:
pre_message['tool_calls'] = tool_calls
pre_reasoning = pre_message.get('reasoning_content')
reasoning = message.get('reasoning_content')
if isinstance(reasoning, str):
pre_message['reasoning_content'] = (pre_reasoning or '') + reasoning
else:
pre_message['content'] = pre_content + content
pre_message['content'] = self._concat_content(pre_content, content)
tool_calls = list(pre_message.get('tool_calls') or []) + list(message.get('tool_calls') or [])
if tool_calls:
pre_message['tool_calls'] = tool_calls
pre_reasoning = pre_message.get('reasoning_content')
reasoning = message.get('reasoning_content')
if isinstance(reasoning, str):
pre_message['reasoning_content'] = (pre_reasoning or '') + reasoning
else:
pre_message['content'] = self._concat_content(pre_content, content)

Comment on lines +400 to +405
elif pre_role == 'assistant' and role == 'assistant' or pre_role == 'user' and role == 'user':
if self.template_backend == 'swift' and pre_role == 'assistant':
self._merge_assistant_messages(pre_message, message)
else:
pre_message['content'] = pre_content + content
messages.pop(i)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Use the safe _concat_content helper when merging consecutive messages in _swift_prepare_inputs to avoid TypeError crashes.

Suggested change
elif pre_role == 'assistant' and role == 'assistant' or pre_role == 'user' and role == 'user':
if self.template_backend == 'swift' and pre_role == 'assistant':
self._merge_assistant_messages(pre_message, message)
else:
pre_message['content'] = pre_content + content
messages.pop(i)
elif pre_role == 'assistant' and role == 'assistant' or pre_role == 'user' and role == 'user':
if self.template_backend == 'swift' and pre_role == 'assistant':
self._merge_assistant_messages(pre_message, message)
else:
pre_message['content'] = self._concat_content(pre_content, content)
messages.pop(i)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class TeleChat3 support to ms-swift by introducing TeleChat3/TelChat3-Coder templates (Swift + Jinja parity), agent tool-call parsing/formatting for both tool-call styles, model registration, and alignment/inference tests. It also threads request tool schemas through multiple inference backends so TeleChat3-Coder can preserve string-typed tool arguments according to schema.

Changes:

  • Implement TeleChat3 + TeleChat3-Coder templates (including thinking normalization, tool-call/tool-response preprocessing, and Swift/Jinja backend parity checks).
  • Add TeleChat3 agent templates (JSON <tool_call> blocks and coder XML param blocks), plus a schema-aware get_toolcall_with_tools(...) hook used by inference engines.
  • Register TeleChat3 models/templates and document them in CN/EN supported-model lists; add focused alignment/inference tests.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/test_align/test_template/test_agent.py Adds TeleChat3/TeleChat3-Coder alignment + inference tests, including schema-aware string-arg parsing assertions.
swift/template/templates/llm.py Introduces TeleChat3Template / TeleChat3CoderTemplate and registers new template metas.
swift/template/constant.py Adds telechat3 and telechat3_coder template type constants.
swift/model/models/telechat.py Registers TeleChat3 dense + coder models with appropriate templates and requirements.
swift/model/models/deepseek.py Registers TeleChat3 MoE (105B-A4.7B) under deepseek_v3 with TeleChat3 template.
swift/model/constant.py Adds telechat3 model type constant.
swift/infer_engine/vllm_engine.py Passes request tools into tool-call parsing for streaming + non-streaming responses.
swift/infer_engine/transformers_engine.py Passes request tools into tool-call parsing for streaming + full inference.
swift/infer_engine/sglang_engine.py Passes request tools into tool-call parsing for streaming + non-streaming responses.
swift/infer_engine/lmdeploy_engine.py Passes request tools into tool-call parsing for streaming + full inference.
swift/infer_engine/infer_engine.py Extends _get_toolcall to accept request tools and use schema-aware agent hook.
swift/infer_engine/grpo_vllm_engine.py Passes request tools into tool-call parsing.
swift/agent_template/telechat3.py Adds TeleChat3 + TeleChat3-Coder agent templates, including schema-aware coder parsing.
swift/agent_template/mapping.py Registers new agent templates in the mapping.
swift/agent_template/base.py Adds backward-compatible get_toolcall_with_tools(response, tools=None) hook.
docs/source/Instruction/Supported-models-and-datasets.md Documents TeleChat3 models/templates in the supported-model list (CN).
docs/source_en/Instruction/Supported-models-and-datasets.md Documents TeleChat3 models/templates in the supported-model list (EN).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread swift/template/templates/llm.py Outdated
Comment on lines +273 to +292
pre_content = pre_message.get('content') or ''
content = message.get('content') or ''
if role == 'assistant':
for key in ['loss', 'loss_scale']:
if key not in pre_message and key not in message:
continue
if key not in pre_message or key not in message or pre_message[key] != message[key]:
raise ValueError(
f'TeleChat3 cannot merge consecutive assistant messages with different `{key}` values. '
'Merge the messages before encoding or use the same value.')
pre_message['content'] = pre_content + content
tool_calls = list(pre_message.get('tool_calls') or []) + list(message.get('tool_calls') or [])
if tool_calls:
pre_message['tool_calls'] = tool_calls
pre_reasoning = pre_message.get('reasoning_content')
reasoning = message.get('reasoning_content')
if isinstance(reasoning, str):
pre_message['reasoning_content'] = (pre_reasoning or '') + reasoning
else:
pre_message['content'] = pre_content + content
Comment on lines +317 to +330
tool_calls = []
for message in messages[i_start:i + 1]:
tool_call = self.agent_template._parse_tool_call(message['content'])
tool_calls.append({'type': 'function', 'function': tool_call})
if i_start > 0 and messages[i_start - 1]['role'] == 'assistant':
assistant_message = messages[i_start - 1]
if assistant_message.get('content') is None:
assistant_message['content'] = ''
assistant_message['tool_calls'] = list(assistant_message.get('tool_calls') or []) + tool_calls
messages[i_start:i + 1] = []
i = i_start
else:
messages[i_start:i + 1] = [{'role': 'assistant', 'content': '', 'tool_calls': tool_calls}]
i = i_start + 1
Comment on lines +468 to +473
has_reasoning = bool(reasoning_content)
reasoning_content = reasoning_content.strip()
content = content.strip()
if preserve_reasoning and has_reasoning:
return f'<think>\n{reasoning_content}\n</think>{content}'
return f'</think>{content}'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants